// S5M Slide Show via Mootools 1.3.1+ framework para uso multi-navegador
// nos dias atuais e no futuro com modificações mínimas.
//
// Completamente baseado no S5 v1.1 publicado sob Domínio Público, disponível
// em http://www.meyerweb.com/eric/tools/s5/ e MooTools publicado sob licença
// Open Source MIT License, disponível em http://mootools.net/

(function S5M()
{
  var defaultView   = 'slideshow';  // modo default de visualização inicial do slide show
  var controlVis    = 'visible';    // visibilidade da interface (ui) de controle
  var useTransition = true;         // uso de efeitos de transição

  var s5mode = true;  // modo de apresentação S5 (bonito) :: outline é feio
  var snum   = 0;     // índice numérico do slide corrente
  var smax   = 1;     // número total de slides na apresentação
  var incpos = 0;     // posição incremental no slide corrente

  var number = null;  // buffer da entrada via teclado de número c/várias finalidades

  var incrementals;   // array de todos os elementos incrementais

  var scroller;       // ponteiro para objeto Fx.Scroll instanciável no modo outline
  var historia;       // ponteiro para objeto da classe Historia
  var myScale;        // ponteiro para objeto da classe Scale
  var runner = null;  // ponteiro para objeto da classe Runner

  var times = {
       exposition: 5000, // duração total da exposição de slide qualquer
    minExposition: 750,  // duração mínima da exposição de elementos incrementais
         fadeTime: 1000  // duração do efeito fade in/out de slides
  };

  var fakeEvent = { stop: function() {} };

  // retorna id do slide como string p/índice numérico fornecido :: string + usada
  function mkId(ndxNumber) { return ['slide', ndxNumber].join('') }

  // atualiza o mostrador do índice de slide corrente e interface de controle
  function updateDisplay() {
    // atualiza o índice de slide selecionado na ui
    $('jumplist').selectedIndex = snum;
    // atualiza o mostrador do número do índice de slide corrente
    $('csHere').set('text', snum);
    // altera a opacidade do mostrador do número do índice de slide corrente
    $('currentSlide').fade(snum ? 1 : 0);
  }

  function warn() { $('currentSlide').fade(.5).fade(1).fade(.5).fade(snum ? 1 : 0) }

  // mostra um slide de acordo com o argumento que usualmente é número
  // inteiro ou o caractere 'j' quando usado o elemento seletor da ui
  function go(step) {
    // checa as restrições básicas
    if ($('slideProj').disabled                   // não usa media screen
        || (step == 0)                            // argumento inválido
        || ((step < 0) && (snum == 0))            // tentando ir aquém do primeiro
        || ((step > 0) && (snum == (smax - 1))))  // ou além do último slide
    {
      if (!$('slideProj').disabled) warn(); // pisca para notificar argumento ilegal
      return;
    }
    // preserva índice do slide a ocultar
    var prev = historia.registra(snum);
    // reinicia todos os elementos incrementais pendentes
    $$(incrementals[snum]).removeClass('current').removeClass('incremental');
    // atualiza o índice do slide corrente
    snum = (step == 'j') ? $('jumplist').value.toInt() : (snum + step).limit(0, smax - 1);
    // atualiza a posição incremental do novo slide corrente
    incpos = (step < 0) ? incrementals[snum].length : 0;
    // há elementos incrementais no novo slide?
    if (incrementals[snum].length) {
      if (incpos == 0) {
        $$(incrementals[snum]).addClass('incremental');
      } else {
        incrementals[snum][incpos - 1].addClass('current');
      }
    }
    // oculta o slide anterior e revela o corrente
    if (useTransition) {
      $(mkId(prev)).setOpacity(.8).fade(0);
      $(mkId(snum)).setOpacity(.2).fade(1);
    } else {
      $(mkId(prev)).setStyle('visibility', 'hidden');
      $(mkId(snum)).setStyle('visibility', 'visible');
      updateDisplay();
    }
    // reinicia o buffer de entrada via teclado e atualiza o mostrador
    displayMsg(number = null);
  }

  // mostra slide com índice baseado no corrente
  function goTo(target) {
    if ((target < smax) && (target != snum)) {
      go(target - snum);
    } else {
      warn();
    }
  }

  // evidencia elemento incremental anterior/próximo no slide corrente
  function subgo(next) {
    if (next) {
      // remove evidência do elemento incremental
      if (incpos > 0) incrementals[snum][incpos - 1].removeClass('current');
      // evidência o novo elemento incremental corrente
      incrementals[snum][incpos].removeClass('incremental').addClass('current');
      incpos++;
    } else {
      incpos--;
      if (incpos >= 0) {
        // remove evidência do elemento incremental
        incrementals[snum][incpos].removeClass('current').addClass('incremental');
      }
      // evidência o novo elemento incremental corrente
      if (incpos > 0) incrementals[snum][incpos - 1].addClass('current');
    }
  }

  // dispara efeito conforme disponibilidade repassando evento fornecido
  function triggerEffects(event) {
    var n, at = $(mkId(snum));
    if ((incrementals[snum].length && !(at = at.getElement('.current')))
        || ((n = ((at = at.getElements('a.trigger')).length - 1)) < 0)) return;
    if (!event || (number == null)) {
      while (n && !at[n].hasClass('preferred')) --n; // pesquisa preferido
    } else {
      n = (number - 1).limit(0, n); // extrai do buffer
      displayMsg(number = null);
    }
    at[n].fireEvent('click', event || fakeEvent);
  }

  // classe de objeto responsável pelo slide show automático para frente/trás
  var Runner = new Class(
    {
      // ajusta tempos de exposição proporcionais ao número de elementos
      // incrementais no slide corrente evitando exposições muito curtas
      adjustDelayTime: function(forward) {
        var len = incrementals[snum].length;
        var lastOrFirst = (forward && (snum == (smax - 1))) || (!forward && (snum == 0));
        this.delayTime = [];
        if (len == 0) {
          // checa se é primeiro ou último slide relativo a direção
          if (lastOrFirst) {
            var t = $(mkId(snum)).getElement('ul.typewriterMenu li a');
            // se existe menu de gatilho(s) de efeito(s) fxTypewriter então
            // posterga fim da apresentação senão zera o tempo de exposição
            this.delayTime.push(t ? t.retrieve('duration') : 0);
          } else {
            this.addTime($(mkId(snum)), 0);
            // se há (ou não) efeito com duração menor que o tempo de exposição
            // então será incrementado deste tempo, senão será incrementado do
            // tempo de transição para evitar exposição muito justa ou longa
            this.delayTime[0] += (this.delayTime[0] < times.exposition) ? times.exposition : times.fadeTime;
          }
        } else if (lastOrFirst && (forward && (incpos == len))) {
          this.delayTime.push(0); // evita cálculo e acionamento desnecessário
        } else {
          // calcula o tempo de exposição unitário/proporcional e checa se é muito curto
          var tm = times.exposition / (len == 1 ? 2 : len);
          if (tm < times.minExposition) tm = times.minExposition;
          // empilha os tempos de exposição
          if (forward) {
            var e, j = len - 1;
            if (lastOrFirst) {
              this.delayTime[0] = 0; // último destacável do último slide
              j--;
            }
            // loop reverso para empilhar tempo de exposição de cada destacável
            while ((e = incrementals[snum][j]) && /incremental|current/.test(e.className)) {
              this.addTime(e, tm);
              j--;
            }
            if (!e || !e.hasClass('current')) {
              this.delayTime.push(tm + 2 * times.fadeTime); // pré-incrementação
            }
          } else {
            // empilha tempo de transição para slide anterior
            this.delayTime[0] = 2 * times.fadeTime;
            // loop para empilhar tempo de exposição de cada destacável
            for (var j = 0; j < incpos; j++) this.addTime(incrementals[snum][j], tm);
            // se todos serão reiniciados então aumenta o tempo de exposição do último
            if (incpos == len) this.delayTime[len] +=  times.fadeTime;
          }
        }
      },

      // empilha tempo de exposição somando duração do efeito se existir 'gatilho'
      addTime: function(elm, time) {
        elm = elm.getElement('a.preferred') || elm.getElement('a.trigger');
        this.delayTime.push(time + (elm ? elm.retrieve('duration') : 0));
      },

      fireEffects: triggerEffects, // dispara efeito 'Fx.Slide'

      // executa o slide show para frente
      runForward: function() {
        // checa se há elementos incrementais a evidenciar
        if (incpos < incrementals[snum].length) {
          subgo(true);
          this.fireEffects();
        } else {
          // checa se o slide corrente é o último
          if (snum == (smax - 1)) {
            this.stop();
            return;
          }
          go(1);
          this.adjustDelayTime(true);
          this.fireEffects();
        }
        // chamada recursiva temporizada com tempo de exposição apropriado
        this.p = this.runForward.delay(this.delayTime.pop(), this);
      },

      // executa o slide show para trás
      runBackward: function() {
        if (incpos > 0) {
          subgo(false);
          this.fireEffects();
        } else {
          if (snum == 0) {
            this.stop();
            return;
          }
          go(-1);
          this.adjustDelayTime(false);
          this.fireEffects();
        }
        this.p = this.runBackward.delay(this.delayTime.pop(), this);
      },

      // inicia execução para frente/trás unicamente
      initialize: function(forward) {
        // inicia pisca-pisca do botão automático
        this.blinker = new Fx.Blinker($('auto'));
        // ajuste inicial de tempo(s) de exposição :: inicializável de qualquer slide
        this.adjustDelayTime(forward);
        // executa na direção escolhida
        this.fireEffects();
        if (forward) {
          this.p = this.runForward.delay(this.delayTime.pop(), this);
        } else { // para trás
          this.p = this.runBackward.delay(this.delayTime.pop(), this);
        }
      },

      // para execução do slide show de qualquer maneira
      stop: function() {
        // cancela execuções pendentes
        clearTimeout(this.p);
        // para efeito pisca-pisca no botão automático
        this.blinker.stop();
        // libera ponteiros para objetos
        runner = this.p = this.blinker = null;
      }
    });

  // retorna o status de execução na tentativa de parar a apresentação automática
  function stopRunner() {
    if (runner) {
      runner.stop();
      return true;
    }
    return false;
  }

  // inicia apresentação automática de slides ou a aborta se estiver em execução
  function auto(backward) { if (!stopRunner()) runner = new Runner(!backward) }

  // alterna o modo de apresentação do slide show
  function toggle() {
    stopRunner();
    var slides = $('slideProj');
    // alterna o status do slide show habilitando/desabilitando o modo outline
    $('outlineStyle').disabled = s5mode = slides.disabled;
    // declara o estilo de todos slides
    var p = { 'visibility' : (s5mode ? 'hidden' : 'visible') };
    if (useTransition) p['opacity'] = (s5mode ? 0 : 1);
    $('presentation').getChildren(/* 'div.slide' */).setStyles(p);
    // testa uso da apresentação mais agradável
    if (s5mode) {
      myScale.fontScale();
      // declara estilo do slide corrente
      p = { 'visibility' : 'visible' };
      if (useTransition) p['opacity'] = 1;
      $(mkId(snum)).setStyles(p);
      // libera o objeto Fx.Scroll se houver para o Garbage Collector
      scroller = instanceOf(scroller, Fx.Scroll);
    } else {
      myScale.fontSize('1em');
      // é necessário criar um objeto scroller?
      if (scroller === true) {
        scroller = new Fx.Scroll(window, {
            transition: Fx.Transitions.Quad.easeInOut,
            wheelStops: false
          });
      }
    }
    // habilita/desabilita folha de estilo do modo S5
    slides.disabled = !slides.disabled;
  }

  // checa se o evento ocorreu na ui de controle
  function chkEventUI(e) { return $('controls').contains(e.target) }

  // observador de eventos de teclado disparando o slide show
  function keys(e) {
    // checa uso do modo visual de apresentação p/processar o evento de teclado
    if (s5mode) {
      switch (e.key) {
        case 'space':
        case 'pagedown':
        case 'right':
        case 'down':
          // cancela apresentação automática se em execução
          stopRunner();
          // há conteúdo no buffer de teclado (mesmo que seja "zero")?
          if (number != null) {
            go(number);
          } else if (incrementals[snum].length && (incpos < incrementals[snum].length)) {
            subgo(true);
          } else {
            go(1);
          }
          return;
        case 'pageup':
        case 'left':
        case 'up':
          stopRunner();
          if (number != null) {
            go(-1 * number);
          } else if (incrementals[snum].length && incpos) {
            subgo(false);
          } else {
            go(-1);
          }
          return;
        case 'linefeed':
        case 'enter':
          stopRunner();
          if (!chkEventUI(e) && (number != null)) goTo(number);
          return;
        case 'x':
          // checa se F9 não pressionado
          if (e.code != 120) {
            stopRunner();
            go(1);
          }
          return;
        case 'z':
          // checa se F11 não pressionado
          if (e.code != 122) {
            stopRunner();
            go(-1);
          }
          return;
        case 'home':
          stopRunner();
          goTo(0); // vai diretamente ao primeiro slide (inicial)
          return;
        case 'end':
          stopRunner();
          goTo(smax - 1); // vai diretamente ao último slide
          return;
        case 'c':
          // alterna visualização da ui de controle se confirmado o código da tecla
          if (e.code == 67) {
            if (controlVis == 'hidden') $('navLinks').fade('toggle');
            return;
          }
          break;
        case 'a':
          // alterna execução da apresentação automática se confirmado o código da tecla
          if (!e.control && (e.code == 65)) {
            auto(e.shift);
            return;
          }
          break;
        case 'f':
          if (!e.control && (e.code == 70)) {
            stopRunner();
            myScale.chgScale(e.shift);
            return;
          }
          break;
        case 'o':
          stopRunner();
          triggerEffects(e);
          return;
        case 'esc':
        case 'delete':
          stopRunner();
          displayMsg(number = null);
          return;
        case 'backspace':
          stopRunner();
          if (e.control) {
            // reapresenta slide acessado anteriormente conforme conteúdo do buffer
            var n = historia.recupera( number );
            if (n != null) goTo(n); else warn();
          } else {
            // se o buffer de teclado não está vazio então remove seu último digito
            if (number != null) number = (number > 9) ? (number / 10).floor() : null;
            displayMsg(number);
          }
          return;
      }
      // Nota: O teste abaixo é executado depois do 'switch' p/evitar conflito
      //       entre teclas numéricas e teclas de função. NÃO ALTERAR ORDEM.
      //
      // checa se tecla numérica pressionada fora da ui de controle
      if (((47 < e.code) && (e.code < 58)) || ((95 < e.code) && (e.code < 106)))
      {
        if (!chkEventUI(e)) {
          stopRunner();
          // compõe número com digito imputado :: base 10 posicional
          number = (number ? number * 10 : 0) + (e.code % 48);
          displayMsg(number);
        }
        return;
      }
    }
    // checa se 't' pressionado sob qualquer modo de apresentação
    if (!e.control && (e.key == 't')) toggle();
  }

  // observador de eventos do mouse disparando o slide show
  function clicker(e) {
    // rejeita eventos da ui de controle
    if (chkEventUI(e)) return true;
    // checa evento click do mouse
    if (e.type == 'click') {
      // click do botão direito não permitido
      if (e.rightClick) return true;
      var t = e.target;
      // checa ocorrência de evento em qualquer elemento de tipo "objeto"
      if (/EMBED|OBJECT|APPLET/.test(t.nodeName)) return true;
      // obtêm se possível, o elemento âncora do alvo do evento ou de algum pai
      if (t.nodeName != 'A') t = t.getParent('a');
      if (t && (t.href.length > 0)) return true;
    } else {
      if (!s5mode) return true;
    }
    // checa se parou execução da apresentação automática
    if (stopRunner()) return true;
    // checa se modificador Shift pressionado durante o click
    // ou exclusivamente rolou para cima para avançar/voltar
    if (!e.shift != !(e.wheel > 0)) { // XOR
      // voltar o que?
      if (e.control || (incpos == 0)) {
        go(-1); // mostra o slide anterior
      } else {
        subgo(false); // evidencia o elemento incremental anterior
      }
    } else {
      // avançar o que?
      if (e.control || (incpos == incrementals[snum].length)) {
        go(1); // mostra o próximo slide
      } else {
        subgo(true); // evidencia o próximo elemento incremental
      }
    }
    return false;
  }

  // procura por slide cujo id ou atributo 'name' seja igual ao argumento
  // retornando o índice numérico correspondente ou null se não encontrado
  function findSlide(idOrName) {
    return ($(idOrName) || $('presentation').getFirst(
      ['div.slide[name=', idOrName, ']'].join(''))) ? idOrName.slice(5).toInt() : null;
  }

  // prepara início da apresentação
  function approach() {
    // adiciona pares (KEY, CODE) extra ao 'dicionário' Event.Keys
    Event.Keys.linefeed = 10;  Event.Keys.pageup = 33;  Event.Keys.pagedown = 34;
    Event.Keys.end      = 35;  Event.Keys.home   = 36;
    // inicia escalamento dinâmico de fontes e imagens
    myScale = new Scale($('presentation'));
    // corrige o comportamento p/navegadores 'non Opera' :: notOperaFix
    $('slideProj').setProperty('media', 'screen');
    $('outlineStyle').disabled = true;
    // inicia registro de visualizações de slides
    historia = new Historia();
    // inicia apresentação conforme requerido na barra de endereço do navegador :: slideJump
    if (window.location.hash) {
      var dest = findSlide(window.location.hash.slice(1));
      if (dest) go(dest - snum);
    }
    if (defaultView == 'outline') toggle();
  }

  // ajusta comportamento de âncoras com destino externo e local
  function fixLinks() {
    $('presentation').getElements('a').each(
      function(a) {
        if (a.href.length) {
          // checa se é link p/âncora com destino externo
          if (a.match('[rel=external]')) {
            // evita que o link ganhe foco impedindo uso do teclado em sequência
            a.addEvent('click', function() { this.blur() })
             // impõe abertura de documento em nova aba/janela
             .addClass('external').target = '_blank';
          } else {
            // extrai o fragmento do endereço
            var j = a.href.lastIndexOf('#');
            var idOrName = (j != -1) ? a.href.slice(j + 1) : null;
            if (idOrName) {
              var broken;
              // checa se é link p/âncora com destino local :: slide
              if (/^slide\d+$/.test(idOrName)) {
                // checa se há elemento âncora destino correspondente
                var ndx = findSlide(idOrName);
                broken = (ndx == null);
                if (!broken) {
                  scroller = true; // criará objeto Fx.Scroll quando necessário
                  a.addEvent('click',
                    function(e) {
                      e.stop(); // evita recarregamento do documento
                      // checa modo de apresentação durante execução :: on the fly
                      if ($('slideProj').disabled) {
                        // obtêm elemento alvo do efeito scroll
                        var t = $(idOrName);
                        // calcula duração do efeito proporcional a distância e com restrição
                        var tm = (t.getCoordinates().top - t.getSize().y - window.getScroll().y).abs().limit(500, 2000);
                        // declara duração de efeito scroll e o executa da posição atual da janela até o elemento alvo
                        scroller.setOptions({'duration': tm}).toElement(t);
                      } else {
                        go(ndx - snum); // modo S5 :: revela o slide
                      }
                      this.blur();
                    });
                }
              } else if (a.hasClass('trigger')) { // efeito Fx.Slide
                // pesquisa elemento 'alvo' pela id ou pelo atributo 'name' no slide pai
                var d = $(idOrName) || a.getParent(['.slide *[name=', idOrName, ']'].join(''));
                broken = (d == null);
                // checa existência do alvo
                if (!broken) {
                  var md = d.hasClass('horizontal') ? 'horizontal' : 'vertical';
                  var tm = (((md == 'horizontal') ? d.getSize().x : d.getSize().y) * 5).limit(500, 1000);
                  // adiciona o efeito c/duração proporcional a dimensão do alvo
                  d.set('slide', {
                            mode: md,
                        duration: tm,
                      transition: Fx.Transitions.Quad.easeOut,
                            link: 'cancel'
                    })
                    // inicialmente oculta o elemento alvo e adiciona classe 'target'
                    // aos descendentes do elemento pai p/sincronia de visibilidade
                    .slide('hide').getParent().getElements('*').addClass('target');
                  // engatilha o efeito 'Fx.Slide' que alterna visualização do alvo
                  a.addEvent('click',
                    function(e) {
                      e.stop();
                      // restringe execução do efeito a 'links' não incrementais
                      if (!this.getParent('.incremental') || !s5mode) d.slide('toggle');
                      this.blur()
                    }).store('duration', tm); // armazena duração p/facilitar apresentação automática
                } else {
                  a.removeClass('trigger');
                }
              }
              // checa link quebrado para remover funcionalidade
              if (broken) {
                var tooltip = (navigator.language == 'pt-BR') ? 'link quebrado' : 'broken link';
                a.addClass('broken').setProperty('title', tooltip).addEvent('click', function() { this.blur(); return false });
              }
            }
          }
        }
      });
  }

  // prepara todos os elementos 'slide' e constrói o mostrador de índice corrente
  function slideLabel() {

    // retorna vetor de elementos incrementais dentro do elemento pai
    // e prepara os elementos encontrados para apresentação
    var getIncrementals = function(parent) {
      var incrementals_array = new Array();
      var listPattern = /[UOD]L/;
      parent.getChildren().each(
        function(child) {
          if (child.hasClass('incremental')) {
            child.removeClass('incremental');
            if (listPattern.test(child.nodeName)) {
              child.getChildren().addClass('incremental');
            } else {
              incrementals_array.include(child);
            }
          }
          if (child.hasClass('show-first')) {
            if (listPattern.test(child.nodeName)) {
              child.removeClass('show-first');
              var c = child.getFirst('.incremental');
              if (c) c.removeClass('incremental');
            } else {
              // uso seguro por não permitir elementos repetidos
              incrementals_array.include(child);
            }
          }
          incrementals_array.append(getIncrementals(child));
        });
      return incrementals_array;
    }

    // coleção de elemetos 'slide'
    var slideColl = $('presentation').getChildren(/* 'div.slide' */);
    // elemento de seleção de slide
    var jl = $('jumplist');
    // obtêm o numero total de slides
    smax = slideColl.length;
    // cria a matriz armazém de elementos incrementais
    incrementals = new Array(smax);
    // atravessa a coleção de slides
    slideColl.each(
      function(elm, ndx) {
        // declara id do elemento slide
        elm.id = mkId(ndx);
        // checa uso do efeito de transição
        if (useTransition) {
          // agrega objeto "tween" para efeito fade in/out do slide
          elm.set('tween', {
                  duration: times.fadeTime,
                transition: Fx.Transitions.Linear,
                      link: /*'cancel' 'ignore'*/ 'chain'
              })
            // agrega observadores de eventos personalizados ao objeto tween
            .get('tween').addEvents({
                'start': function() {
                    // degrada o mostrador de índices quando o efeito fade out inicia
                    if (elm.getOpacity() > 0) $('currentSlide').fade(.3);
                  },
                'complete': function() {
                    if (elm.getOpacity() > 0) {
                      // atualiza o mostrador de índices quando o efeito fade in completa
                      updateDisplay();
                      // assegura opacidade "zero" aos demais elementos slide :: devido a processadores lentos
                      $('presentation').getChildren(['div.slide[id!=slide', snum, ']'].join('')).setOpacity(0);
                    }
                  }
              });
        }
        // tenta obter o primeiro elemento filho :: usualmente é elemento heading
        var h = elm.getFirst();
        if (h) {
          jl.options[jl.length] = new Option([ndx, h.get('text')].join(' : '), ndx);
        }
        // obtêm vetor de elementos incrementais neste slide
        incrementals[ndx] = getIncrementals(elm);
        // se requisitado efeito fxTypewriter então prepara efeito(s) e menu de gatilho(s)
        var c = elm.getElements('*.fxTypewriter');
        if (c.length > 0) {
          var menu = new Element('ul.typewriterMenu').inject(elm.getFirst(), 'after');
          var soma = 0;
          c.each(function(phrase, index) {
              phrase.store('next', index + 1 /* DOM count */ + 1 /* next index */ );
              var w = new Fx.Typewriter(phrase).addEvents({
                     'start': function() { this.element.addClass('fakeCursor') },
                  'complete': function() {
                    this.element.removeClass('fakeCursor');
                    // se em apresentação automática, desencadeia efeito seguinte
                    if (runner) {
                      var next = this.element.retrieve('next');
                      if (next < menu.childNodes.length)
                        menu.getFirst(['li:nth-child(', next, ') a'].join('')).fireEvent('click', fakeEvent);
                    }
                  }
                });
              soma += w.options.duration;
              var text = ['FRASE ', index + 1].join('');
              menu.grab(new Element('li').grab(
                new Element('a.trigger', {
                   href: '#',
                   text: text,
                  title: ['clique para (re)executar efeito na ', text].join('')
                })
                .addEvent('click', function(e) { e.stop(); w.toggle(); this.blur() })));
            });
          // armazena a soma das durações p/apresentação automática
          menu.getFirst('li a').store('duration', soma);
          // adiciona botão p/reiniciar todas as mensagens
          menu.grab(new Element('li').grab(
              new Element('a', {
                  href: '#',
                  text: 'RESET ALL'
              })
              .addEvent('click',
                function(e) {
                  e.stop();
                  this.getParent('div.slide').getElements('*.fxTypewriter').each(
                    function(phrase) { phrase.set('text', '') });
                  this.blur();
                })
            ));
        }
      });
    // constrói o mostrador de índice do slide corrente unicamente
    $('currentSlide').set('html', [
        '<span id="csHere">0<\/span><span id="csSep"> ', ((navigator.language == 'pt-BR') ? 'de' : 'of'), ' <\/span>',
        '<span id="csTotal">', (smax - 1), '<\/span>'].join('')
      ).set('tween', {
          duration: 250,
        transition: Fx.Transitions.Linear,
              link: 'chain'
      }).fade(0);
  }

  // constrói a interface do usuário para controle da apresentação
  function createControls() {
    var tooltips = (navigator.language == 'pt-BR') ? ['alterna visual da apresentação', 'aumenta tamanho da fonte (Shift para reduzir)', 'apresentação automática (Shift para retro-apresentação)', 'apresenta slide anterior', 'apresenta próximo slide'] : ['toggle presentation mode', 'increase font size (Shift to decrease)', 'automatic presentation (Shift to backward)', 'show previous slide', 'show next slide'];
    $('controls').set('html', [
      '<form action="#" id="controlForm"><div id="navLinks">',
      '<a href="#" accesskey="t" id="toggle">&#216;<\/a>',
      '<a href="#" accesskey="f" id="font">F<\/a>',
      '<a href="#" accesskey="a" id="auto">A<\/a>',
      '<a href="#" accesskey="z" id="prev">&laquo;<\/a>',
      '<a href="#" accesskey="x" id="next">&raquo;<\/a>',
      '<div id="navList"><select id="jumplist"><\/select><\/div><\/div><\/form>'
      ].join(''))
      .getElements('a').each(
        function(a, ndx) { a.setProperty('title', tooltips[ndx]) });
    if (controlVis == 'hidden') {
      // agrega observador de eventos do mouse a ui de controle
      $('controlForm').addEvents({
          'mouseenter': function() { $('navLinks').fade('in') },
          'mouseleave': function() { $('navLinks').fade('out') }
        });
      // agrega objeto "tween" para efeito fade in/out e executa o fade out
      $('navLinks').set('tween', {
            duration: Fx.Durations.long,
          transition: Fx.Transitions.Linear,
                link: 'cancel'
        }).fade(0);
    }
    // agrega observador de eventos a cada elemento de controle
    $('toggle').addEvent('click', function(e) { e.stop(); toggle(); this.blur() });
    $('prev').addEvent('click', function(e) { e.stop(); go(-1); this.blur() });
    $('next').addEvent('click', function(e) { e.stop(); go(1); this.blur() });
    $('jumplist').addEvent('change', function(e) { e.stop(); go('j'); this.blur() });
    $('auto').addEvent('click', function(e) { e.stop(); auto(e.shift); this.blur() });
    $('font').addEvent('click', function(e) { e.stop(); stopRunner(); myScale.chgScale(e.shift); this.blur() });
  }

  // sobrescreve valores default e checa/complementa estrutura do documento
  function defaultCheck() {

    var getMetaContent = function(value) {
      var meta = $(document.head).getElement(['meta[name=', value , ']'].join(''));
      return meta ? meta.content : null;
    };

    // sobrescreve valor default do modo de visualização inicial
    var content = getMetaContent('defaultView');
    if (/slideshow|outline/.test(content)) defaultView = content;
    // sobrescreve valor default da visibilidade da ui de controle
    content = getMetaContent('controlVis');
    if (/visible|hidden/.test(content)) controlVis = content;
    // sobrescreve valor default do uso de efeitos de transição nas trocas de slides
    content = getMetaContent('useTransition');
    if (content) useTransition = /true|yes/.test(content);

    var create = function(name, another, position) {
      return new Element(name).inject(another, position || 'bottom');
    };

    // cria elemento 'div.layout' se não existe
    var elm = $(document.body).getElement('div.layout') || create('div.layout', document.body, 'top');
    // cria elemento 'div#controls' se não existe
    elm = $('controls') || create('div#controls', elm, 'top');
    // cria elemento 'div#currentSlide' se não existe
    elm = $('currentSlide') || create('div#currentSlide', elm, 'after');
    // cria elemento 'div#header' se não existe
    elm = $('header') || create('div#header', elm, 'after');
    // checa que não existe elemento 'h1' dentro do 'div#header'
    if (!elm.getElement('h1')) {
      // tenta criar o elemento cujo texto é o nome do autor
      content = getMetaContent('author');
      if (content) create('h1', elm).set('text', content);
    }
    // cria elemento 'div#footer' se não existe
    elm = $('footer') || create('div#footer', elm, 'after');
    // declara o 'id' da 'div.presentation'
    $(document.body).getElement('div.presentation').id = 'presentation';
    // checa que não existe elemento 'h1' na 'div#footer'
    if (!elm.getElement('h1')) {
      // tenta criar o elemento cujo texto é o título da apresentação
      var h = $('presentation').getFirst('div.slide h1');
      if (h) {
        var txt = h.get('text');
        if (txt) create('h1', elm, 'top').set('text', txt);
      }
    }
    // cria elemento 'h2' na 'div#footer' se necessário e declara seu 'id'
    (elm.getElement('h2') || create('h2', elm)).id = 'buffer';

    // mostra data do sistema
    var datePattern = '%x %X';
    if (navigator.language == 'pt-BR') {
      Locale.use('pt-BR');
      datePattern = '%a %d/%b/%Y %X %Z%z';
    }
    displayMsg(new Date().format(datePattern));
  }

  function displayMsg(msg) { $('buffer').set('text', msg) }

  window.addEvent(
    'domready', function() {
      defaultCheck();
      createControls();
      slideLabel();
      fixLinks();
      approach();
      document.addEvents({
        'keyup': keys,
        'click': clicker,
        'mousewheel': clicker
      });
      window.addEvents({
        'resize': function() { if (s5mode) myScale.scaleAll() },
        'unload': function() {
          myScale.preserve();
          // evita vazamentos de memória liberando ponteiros p/elementos incrementais
          if (!incrementals) return;
          for (var i = 0; i < incrementals.length; i++) {
            var a = incrementals[i];
            for (var j = 0; j < a.length; j++) a[j] = null;
            a.empty();
          }
          incrementals.empty();
        }
      });
    });
})();